package client.network;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;

/*
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
 */

import common.Command;
import common.TreeModelExplorer;


/**
 * Classe écoutant et contrôlant les commandes issues du serveur
 * @version 1.0
 */
public class ClientCommand extends Thread{

	private Client _client;
	private Socket m_commands_socket;
	private BufferedWriter m_writer;
	private BufferedReader m_reader;
	private ClientSending m_sending;
	private boolean _isAuthentified;
	private int _authTryNbr;
	public final static int MAX_AUTHENTIFICATION_TRY = 3;

	/**
	 * Constructeur de classe Client_Command (implémentation d'un socket client et connexion au serveur)
	 * @param client
	 * Le client chargé de faire le lien entre ce thread des commandes et le thread de gestion des données
	 * @param sock
	 * La socket utilisée par ce thread pour communiquer les commandes au serveur
	 */
	public ClientCommand(Client client, Socket sock) {
		_client = client;
		_isAuthentified = false;
		_authTryNbr = 0;
		m_commands_socket = sock;
		m_sending = null;
		m_writer = null;
		m_reader = null;
	}

	/**
	 * Thread du contrôle des commandes côté client : réception des commandes du serveur
	 */
	public void run() {
		try{
			send_command(Command.SERVER_TREE);
			while(true){
				String commande = m_reader.readLine();
				if(commande.contains(Command.FERMETURE_SERVEUR)){										/* Le serveur se déconnecte , donc le client se déconnecte */
					m_commands_socket.close();
					System.exit(0);
				}
				if(commande.contains(Command.SYNCHRO_FILE_READY)){										/* Le serveur est prêt à recevoir des fichiers */
					String commandes[] = commande.split("<>");
					String file_path[] = commandes[0].split("/]");
					String file_dest[] = commandes[1].split("/]");
					if(_client.get_sending_socket() == null){
						Socket s = _client.get_sending_socket();
						s = new Socket(m_commands_socket.getInetAddress(), 9002);
						_client.set_sending_socket(s);
						m_sending = new ClientSending(_client.get_sending_socket(), _client, file_path[1]);
					}
					m_sending.send_file_to_server(file_path[1], file_dest[1]);
				}
				if(commande.contains(Command.SERVER_TREE)){
					_client.get_controller().getMainWindow().get_serverExplorer().setModel(get_server_tree());
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	 * Envoi d'une commande simple au serveur
	 * @param commande
	 */
	public void send_command(String commande){
		try{
			m_writer.write(commande);
			m_writer.newLine();
			m_writer.flush();
		}catch(IOException e){
			e.printStackTrace();
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	/**
	 * Demande d'envoi d'un fichier à la racine du serveur
	 * @param commande
	 */
	public void send_command_file(String commande){
		if(commande.contains(Command.SYNCHRO_FILE)){
			String value[] = commande.split("/]");
			String file_path = value[1];
			File f = new File(file_path);
			long date = f.lastModified();
			long size = f.length();
			send_command(commande+"<>"+Command.FILE_DATE+String.valueOf(date)+"<>"+Command.FILE_SIZE+String.valueOf(size));
		}
	}

	/**
	 * Demande d'envoi d'un fichier dans un dossier spécifique du serveur
	 * @param commande
	 * @param path_dest
	 */
	public void send_command_file(String commande, String path_dest){
		if(commande.contains(Command.SYNCHRO_FILE)){
			String value[] = commande.split("/]");
			String file_path = value[1];
			File f = new File(file_path);
			long date = f.lastModified();
			long size = f.length();
			send_command(commande+"<>"+Command.FILE_DATE+String.valueOf(date)+"<>"+Command.FILE_SIZE+String.valueOf(size)+"<>"+Command.FILE_DEST+path_dest);
		}
	}

	/**
	 * Envoi d'un dossier et de son contenu (fonction récursive en fonction du contenu du dossier)
	 * @param commande
	 */
	public void send_command_dir(String commande, String pere_path){
		if(commande.contains(Command.SYNCHRO_DIR)){
			String value[] = commande.split("/]");
			String file_path = value[1];
			File f = new File(file_path);
			File v[] = f.listFiles();
			String create_dir = "";
			if(pere_path != "")
				create_dir = file_path.replaceFirst(pere_path, ".");
			else
				create_dir = file_path.replaceAll(_client.get_racine_path(), ".");
			send_command(Command.CREATE_DIR + create_dir);
			
			// Envoi des fichiers
			for(int i=0; i<v.length; ++i){
				if(v[i].isFile()){
					send_command_file(Command.SYNCHRO_FILE+v[i].getPath(), create_dir);
				}
			}
			// Envoi des dossiers (récursivement)
			for(int i=0; i<v.length; ++i){
				if(v[i].isDirectory()){
					send_command_dir(Command.SYNCHRO_DIR+v[i].getPath(), "");
				}
			}
		}
	}

	/**
	 * Initialise les flux d'entrée et de sortie 
	 * @throws IOException
	 */
	public void initializeInputOutput() throws IOException
	{
		if(m_commands_socket.isConnected())
		{
			if (m_writer == null)
				m_writer = new BufferedWriter(new OutputStreamWriter(m_commands_socket.getOutputStream()));
			if (m_reader == null)
				m_reader = new BufferedReader(new InputStreamReader(m_commands_socket.getInputStream()));
		}
	}

	/**
	 * S'identifie auprès du serveur en utilisant le couple id/password spécifié.
	 * @param id
	 * L'identifiant de l'utilisateur.
	 * @param password
	 * Le mot de passe de l'utilisateur.
	 * @return
	 * True si l'authentification est valide, false sinon.
	 * @throws IOException
	 * @throws AlreadyAuthentifiedException
	 * @throws MaxLoginTryException
	 */
	public boolean authentify(String id, String password) throws IOException, AlreadyAuthentifiedException, MaxLoginTryException
	{
		boolean success = false;

		// L'utilisateur est déjà authentifié
		if (_isAuthentified) throw new AlreadyAuthentifiedException();

		// Le nombre maximum de tentatives de connexion a été atteint
		if (_authTryNbr >= MAX_AUTHENTIFICATION_TRY) throw new MaxLoginTryException(MAX_AUTHENTIFICATION_TRY);

		// La personne n'est pas déjà authentifiée
		// et le nombre maximum de tentatives n'a pas été atteint.
		String msg = null;

		// Envoi d'une requête d'authentification au serveur
		m_writer.write(Command.PSEUDO + id + "<>" + Command.PASSWORD + password);
		m_writer.newLine();
		m_writer.flush();

		// Lecture de la réponse du serveur
		msg = m_reader.readLine();
		if (msg != null)
		{
			// Le serveur accepte la connexion
			if(msg.contains(Command.CONNEXION_ACCEPTED))
			{
				_isAuthentified = true;
				success = true;
			}
			// la connexion est refusée, le nombre de tentatives restantes diminue
			else
			{
				_authTryNbr++;
			}
		}
		return success;
	}

	/**
	 * Ferme proprement toutes les ressources de ce clientCommand
	 */
	public void close()
	{
		// Ferme la socket et normalement l'InputStream et l'OutputStream qui y sont liés
		if (m_commands_socket != null)
		{
			try {
				m_commands_socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			m_commands_socket = null;
		}

		// On teste si l'InputStream et l'OutputStream sont bien fermés
		if (m_reader != null)
		{
			try {
				m_reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			m_reader = null;
		}

		if (m_writer != null)
		{
			try {
				m_writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			m_writer = null;
		}
	}
	
	/**
	 * Récupère l'arborescence locale du serveur
	 * @return TreeModelExplorer
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public TreeModelExplorer get_server_tree() {
		try{
			ObjectInputStream ois = new ObjectInputStream(m_commands_socket.getInputStream());
			TreeModelExplorer _treeModel = (TreeModelExplorer) ois.readObject();
			return _treeModel;
		}catch(IOException e){
			send_command(Command.SERVER_TREE);
		}catch(ClassNotFoundException e){
			send_command(Command.SERVER_TREE);
		}
		return null;
	}
	
	public Client get_client(){
		return _client;
	}

}
